/**
 * Copyright (C) 2008+ Spark Zheng
 *
 * @file	src/confloose.c
 * @brief
 *      loose config module, config file must like:
 *		#this is an annotation
 *		[module1]
 *		key1 = value1
 *		key2 = value2 \
 *		       value2-continued (need notice the space)
 *		[module2]
 *		key3 = value3-1, value3-2
 *	Caution, Not thread-safe
 *
 * @see		dlib
 * @module      dlib
 * @history
 *      version 0.0.1   Spark Zheng init confloose module
 */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "confloose.h"
#include "mempool.h"
#include "str.h"

static inline int _confl_insert_field(dlib_confl_t *confl, const char *field);
static inline int _confl_insert_item(dlib_confl_t *confl, const char *field,
				     const char *key, const char *value);
static int _confl_load_file(const char *file, dlib_confl_t *confl);

static inline int _confl_insert_field(dlib_confl_t *confl, const char *field)
{
	int ret = 0;
	int len = 0;
	dlib_confl_field_t *tpos = NULL;
	hlist_node_t *pos = NULL;
	dlib_confl_field_t *new = NULL;
	
	if (confl == NULL || field == NULL) return -1;
	
	/* typical insert logic in HLIST */
	HLIST_FOREACH_ENTRY(tpos, pos, &confl->field_head, field_node) {
		ret = str_compare(field, tpos->field_name);
		if (ret == 0) return -E_CONFL_FIELD_EXIST;
		else if (ret < 0) break;
		else continue;
	}

	len = strlen(field);
	new = (dlib_confl_field_t *)dmalloc(sizeof(*new));
	HLIST_HEAD_INIT(&new->item_head);
	new->item_num = 0;
	new->field_name = (char *)dmalloc(len + 1);
	memcpy(new->field_name, field, len);
	
	/* add to hlist head */
	if (pos == NULL && tpos == NULL) {
		HLIST_ADD(&confl->field_head, &new->field_node);
	} /* add to hlist end */
	else if (pos == NULL && tpos != NULL) {
		HLIST_INSERT_AFTER(&tpos->field_node, &new->field_node);
	} /* add before tpos */
	else {
		HLIST_INSERT_BEFORE(&tpos->field_node, &new->field_node);
	}
	
	confl->field_num++;

	return 0;
}

static inline int _confl_insert_item(dlib_confl_t *confl, const char *field,
				     const char *key, const char *value)
{
	int ret = 0;
	int key_len = 0;
	int val_len = 0;
	
	dlib_confl_field_t *f_tpos = NULL;
	hlist_node_t *f_pos = NULL;
	dlib_confl_item_t *i_tpos = NULL;
	hlist_node_t *i_pos = NULL;
	dlib_confl_item_t *new = NULL;
	
	if (confl == NULL || field == NULL || key == NULL || value == NULL)
		return -1;
	
	HLIST_FOREACH_ENTRY(f_tpos, f_pos, &confl->field_head, field_node) {
		ret = str_compare(field, f_tpos->field_name);
		if (ret == 0) { /* choose the field */
			HLIST_FOREACH_ENTRY(i_tpos, i_pos, &f_tpos->item_head,
					    item_node) {
				ret = str_compare(key, i_tpos->key);
				if (ret == 0) return -E_CONFL_ITEM_EXIST;
				else if (ret < 0) break;
				else continue;
			}/* end the item loop */
			
			key_len = strlen(key);
			val_len = strlen(value);
			new = (dlib_confl_item_t *)dmalloc(sizeof(*new));
			new->key = (char *)dmalloc(key_len + 1);
			memcpy(new->key, key, key_len);
			new->value = (char *)dmalloc(val_len + 1);
			memcpy(new->value, value, val_len);
			
			/* add to hlist head */
			if (i_pos == NULL && i_tpos == NULL) {
				HLIST_ADD(&f_tpos->item_head, &new->item_node);
			} /* add to hlist end */
			else if (i_pos == NULL && i_tpos != NULL) {
				HLIST_INSERT_AFTER(&i_tpos->item_node,
						   &new->item_node);
			} /* add before tpos */
			else {
				HLIST_INSERT_BEFORE(&i_tpos->item_node,
						    &new->item_node);
			}
			
			f_tpos->item_num++;
			confl->item_num++;
			
			break;
		} else if (ret < 0) {
			return -E_CONFL_FIELD_NOTEXIST;
		} else {
			continue;
		}
	}/* end the field loop */
	
	return 0;
}

static int _confl_load_file(const char *file, dlib_confl_t *confl)
{
	int ret = 0;
	int last = 0;
	int line = 0;
	int line_len = 0;
	char *equal = NULL;
	char *key = NULL;
	char *value = NULL;
	
	FILE *cfp;
	char buffer[BUF_LEN + 1] = {0};
	
	char *tmp = NULL;
	int field_flag = 0;
	char field[BUF_LEN + 1] = {0};
	
	if (file == NULL || confl == NULL) {
		return -1;
	}
	
	/* begin load config file */
	if ((cfp = fopen(file, "r")) == NULL) {
		fprintf(stderr, "CONFL ERR: fopen error,unable to open %s:%s\n",
			file, strerror(errno));
		return -10;
	}
	
	while (fgets(buffer + last, BUF_LEN - last, cfp) != NULL) {
		line++;
		line_len = strlen(buffer) - 1;
		/* unexpected too long lines */
		if (buffer[line_len] != '\n') {
			fprintf(stderr, "CONFL ERR: input line %d " \
				"in file %s is too long.\n", line, file);
			fclose(cfp);
			return -11;
		}
		/* comment-line */
		if (buffer[0] == '#' || (buffer[0] == '/' && buffer[1] == '/')) {
			continue;
		}

		while (line_len >= 0 && (buffer[line_len] == '\n'
					 || isspace(buffer[line_len]))) {
			buffer[line_len] = '\0';
			line_len--;
		}
		/* all space charactor */
		if (line_len == -1) {
			continue;
		}
		/* mutli-line */
		if (buffer[line_len] == '\\') {
			last = line_len;
			continue;
		} else {
			last = 0;
		}
		
		/* normal-line */
		/* read the field */
		if (buffer[0] == '[' && buffer[line_len] == ']') {
			field_flag = 1;
			buffer[line_len] = '\0';
			tmp = buffer + 1;
			str_trim(tmp);
			snprintf(field, BUF_LEN, "%s", tmp);
			/* insert field */
			
			ret = _confl_insert_field(confl, field);
			if (ret == -E_CONFL_FIELD_EXIST) {
				fprintf(stderr, "CONFL WARN: field %s exist at "
					"line %d file %s\n", field, line, file);
			}
			
			continue;
		}
		
		/* read the item */
		if ((equal = strchr(buffer, '=')) == NULL) {
			fprintf(stderr, "CONFL ERR: input line %d " \
				"in file %s is unexpected.\n", line, file);
			fclose(cfp);
			return -12;
		}
		
		if (field_flag == 0) {
			field_flag = 1;
			ret = _confl_insert_field(confl, DEF_FIELD_NAME);
			snprintf(field, BUF_LEN, "%s", DEF_FIELD_NAME);
		}
		
		key = buffer;
		value = equal + 1;
		*equal = '\0';

		str_trim(key);
		if (*key == 0) {
			fprintf(stderr, "CONFL ERR: unknow item sat " \
				"line %d in file %s.\n", line, file);
			fclose(cfp);
			return -13;
		}
		
		str_trim(value);
		if (*value == 0) {
			fprintf(stderr, "CONFL WARN: unvalid value for item " \
				"%s at line %d in file %s, Just ignore it.\n", \
				key, line, file);
			continue;
		}
		
		ret = _confl_insert_item(confl, field, key, value);
		if (ret == -E_CONFL_ITEM_EXIST) {
			fprintf(stderr, "CONFL WARN: key[field] %s[%s] exist at"
				" line %d file %s, first takes effect.\n",
				key, field, line, file);
		} else if (ret == -E_CONFL_FIELD_NOTEXIST) {
			fprintf(stderr, "CONFL WARN: key[field] %s[%s] field "
				"not exist at line %d file %s.\n",
				key, field, line, file);
		}
	}/* end while fgets */

	fclose(cfp);
	
	return 0;
}

dlib_confl_t *dlib_confl_init(const char *file)
{
	dlib_confl_t *confl = NULL;
	
	if (file == NULL) return NULL;
	
	confl = (dlib_confl_t *)dmalloc(sizeof(*confl));
	
	HLIST_HEAD_INIT(&confl->field_head);
	confl->field_num = 0;
	confl->item_num = 0;
	
	if (_confl_load_file(file, confl) < 0) {
		dlib_confl_exit(confl);
		return NULL;
	}
	
	return confl;
}

void dlib_confl_exit(dlib_confl_t *confl)
{
	dlib_confl_field_t *f_tpos;
	hlist_node_t *f_pos;
	hlist_node_t *f_n;
	dlib_confl_item_t *i_tpos;
	hlist_node_t *i_pos;
	hlist_node_t *i_n;
	
	if (confl == NULL) return;
	
	HLIST_FOREACH_ENTRY_SAFE(f_tpos, f_pos, f_n,
				 &confl->field_head, field_node) {
		if (f_tpos->field_name != NULL) {
			dfree(f_tpos->field_name);
			f_tpos->field_name = NULL;
		}
		
		HLIST_FOREACH_ENTRY_SAFE(i_tpos, i_pos, i_n,
					 &f_tpos->item_head, item_node) {
			if (i_tpos->key != NULL) {
				dfree(i_tpos->key);
				i_tpos->key = NULL;
			}
			if (i_tpos->value != NULL) {
				dfree(i_tpos->value);
				i_tpos->value = NULL;
			}
			
			dfree(i_tpos);
			i_tpos = NULL;
		} /* end for item */
		
		dfree(f_tpos);
		f_tpos = NULL;
	}/* end for field */
	
	dfree(confl);
	confl = NULL;
	
	return;
}

int dlib_confl_get_integer(dlib_confl_t *confl, const char *field,
			   const char *key, long *integer)
{
	int ret = 0;
	dlib_confl_field_t *f_tpos;
	hlist_node_t *f_pos;
	dlib_confl_item_t *i_tpos;
	hlist_node_t *i_pos;
	const char *field_bak = field;
	
	if (confl == NULL || key == NULL || integer == NULL)
		return -1;
	if (field == NULL)
		field_bak = DEF_FIELD_NAME;
	
	HLIST_FOREACH_ENTRY(f_tpos, f_pos, &confl->field_head, field_node) {
		ret = str_compare(field_bak, f_tpos->field_name);
		
		if (ret == 0) { /* choose the field */
			HLIST_FOREACH_ENTRY(i_tpos, i_pos, &f_tpos->item_head,
					    item_node) {
				ret = str_compare(key, i_tpos->key);
				if (ret == 0) {
					*integer = atol(i_tpos->value);
					return 0;
				} else if (ret < 0) {
					return -E_CONFL_ITEM_NOTEXIST;
				} else {
					continue;
				}
			}/* end the item loop */
			return -E_CONFL_ITEM_NOTEXIST;
		} else if (ret < 0) {
			return -E_CONFL_FIELD_NOTEXIST;
		} else {
			continue;
		}
	}/* end the field loop */

	return -E_CONFL_FIELD_NOTEXIST;
}

char *dlib_confl_get_string(dlib_confl_t *confl, const char *field,
			    const char *key, int type)
{
	int ret = 0;
	int val_len = 0;
	char *buffer = NULL;
	
	dlib_confl_field_t *f_tpos;
	hlist_node_t *f_pos;
	dlib_confl_item_t *i_tpos;
	hlist_node_t *i_pos;
	const char *field_bak = field;

	if (confl == NULL || key == NULL)
		return NULL;

	if (field == NULL)
		field_bak = DEF_FIELD_NAME;
	
	HLIST_FOREACH_ENTRY(f_tpos, f_pos, &confl->field_head, field_node) {
		ret = str_compare(field_bak, f_tpos->field_name);
		
		if (ret == 0) { /* choose the field */
			HLIST_FOREACH_ENTRY(i_tpos, i_pos, &f_tpos->item_head,
					    item_node) {
				ret = str_compare(key, i_tpos->key);
				if (ret == 0) {
					if (type == CONFL_STR_MOVE) { /* MOVE */
						buffer = i_tpos->value;
						i_tpos->value = NULL;
						return buffer;
					} else if (i_tpos->value != NULL) {
						val_len = strlen(i_tpos->value);
						buffer = (char *)
							 dmalloc(val_len + 1);
						memcpy(buffer, i_tpos->value,
						       val_len);
						buffer[val_len] = 0;
						return buffer;
					} else {
						return NULL;
					}

				} else if (ret < 0) {
					return NULL;
				} else {
					continue;
				}
			}/* end the item loop */
		} else if (ret < 0) {
			return NULL;
		} else {
			continue;
		}
	}/* end the field loop */
	
	return NULL;
}

void dlib_confl_dump(dlib_confl_t *confl)
{
	int i = 0;
	int j = 0;
	dlib_confl_field_t *f_tpos;
	hlist_node_t *f_pos;
	dlib_confl_item_t *i_tpos;
	hlist_node_t *i_pos;
	
	if (confl == NULL) return;
	
	fprintf(stdout, "CONFL Dump Out:\n"
		"    confl field [%u], item [%u]:\n",
		confl->field_num, confl->item_num);
	
	HLIST_FOREACH_ENTRY(f_tpos, f_pos, &confl->field_head, field_node) {
		j = 0;
		if (f_tpos->field_name != NULL) {
			fprintf(stdout, "field[%d]: name: '%s', item_num %u:\n",
				i, f_tpos->field_name, f_tpos->item_num);
		}
		
		HLIST_FOREACH_ENTRY(i_tpos, i_pos, &f_tpos->item_head, item_node) {
			fprintf(stdout, "    field[%d] item[%d]: ", i, j);
			if (i_tpos->key != NULL) {
				fprintf(stdout, "key: '%s' ", i_tpos->key);
			}
			if (i_tpos->value != NULL) {
				fprintf(stdout, ",value: '%s'", i_tpos->value);
			}
			fprintf(stdout, "\n");
			
			j++;
		} /* end for item */
		
		i++;
	}/* end for field */

	return;
}
